using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using Microsoft.Dss.Core.Attributes;

namespace VEXRobotics
{
    /// <summary>
    /// The base vex command format in which all commands inherit from
    /// </summary>
    [DataContract]
    public class VEXCommand
    {
        private byte[] _data;
        private VEXHelper.VEXCommandCode _commandCode;

        // Maximum size of the data block
        [DataMember]
        public static readonly int MaxCommandLength = 64;

        /// <summary>
        /// The VEX Command Code
        /// </summary>
        internal virtual VEXHelper.VEXCommandCode VEXCommandCode
        {
            get { return _commandCode; }
            set { _commandCode = value; }
        }

        /// <summary>
        /// Internal Data Structure
        /// </summary>
        internal virtual byte[] Data
        {
            get { return _data; }
            set { _data = value; }
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public VEXCommand()
        {
        }

        /// <summary>
        /// Construct a new instance
        /// </summary>
        /// <param name="command">Command to be sent</param>
        /// <param name="dataLength">Length of the data packet</param>
        public VEXCommand(VEXHelper.VEXCommandCode command, int dataLength)
        {
            this._commandCode = command;
            if (dataLength > 0)
                this._data = new byte[dataLength];
            else
                this._data = null;
        }

        /// <summary>
        /// Construct an instance using existing data (Usually a response)
        /// </summary>
        /// <param name="command">Command executed</param>
        /// <param name="dataLength">Length of the data packet</param>
        /// <param name="data">Data received</param>
        public VEXCommand(VEXHelper.VEXCommandCode command, int dataLength, byte[] data)
        {
            this._commandCode = command;
            this.Data = data;

            // make sure data was specified
            if ((data != null) && (data.Length == dataLength))
            {
                this.Data = data;
            }
            // Data was not specified or the wrong length
            else if (dataLength > 0)
            {
                // Allocate the correct length and copy the data that we got
                this.Data = new byte[dataLength];
                if (data != null)
                {
                    for (int ix = 0; ix < dataLength && ix < data.Length; ix++)
                        this.Data[ix] = data[ix];
                }
            }
        }

        /// <summary>
        /// Get the bit at the position specified
        /// </summary>
        /// <param name="startPosition">Byte offset</param>
        /// <param name="bit">Bit to query (0 to 7)</param>
        /// <returns>True if set</returns>
        public bool GetBit(int startPosition, int bit)
        {
            // Do we have room in the buffer?
            if (_data == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition >= _data.Length)
                throw new ArgumentOutOfRangeException("startPosition");

            // Validate the bit offset
            if (bit < 0 || bit > 7)
                throw new ArgumentOutOfRangeException("bit");

            byte aByte = _data[startPosition];
            return 0 != (aByte & 1 << bit);
        }

        /// <summary>
        /// Set the bit specified
        /// </summary>
        /// <param name="startPosition">Byte to set bit in</param>
        /// <param name="bit">Bit to set (0 to 7)</param>
        /// <param name="value">Value to set</param>
        public void SetBit(int startPosition, int bit, bool value)
        {
            // Do we have room in the buffer?
            if (value == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition > _data.Length - 1)
                throw new ArgumentOutOfRangeException("startPosition");

            // Validate the bit offset
            if (bit < 0 || bit > 7)
                throw new ArgumentOutOfRangeException("bit");

            if (value)
            {
                _data[startPosition] |= (byte)(1 << bit);
            }
            else
            {
                _data[startPosition] &= (byte)(~(1 << bit));
            }
        }

        /// <summary>
        /// Get the byte specified
        /// </summary>
        /// <param name="startPosition">Byte to retrieve</param>
        /// <returns>Value of the byte</returns>
        public byte GetSByte(int startPosition)
        {
            // Do we have room in the buffer?
            if (_data == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition >= _data.Length)
                throw new ArgumentOutOfRangeException("startPosition");

            return _data[startPosition];
        }
        /// <summary>
        /// Set the byte specified
        /// </summary>
        /// <param name="startPosition">Byte offset</param>
        /// <param name="value">Value to set</param>
        /// <returns></returns>
        public void SetSByte(int startPosition, int value)
        {
            // Do we have room in the buffer?
            if (value == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition > _data.Length - 1)
                throw new ArgumentOutOfRangeException("startPosition");

            // Set the value
            _data[startPosition] = (byte)(value & 0xff);
        }

        /// <summary>
        /// Get the short value
        /// </summary>
        /// <param name="startPosition">Offset of the short</param>
        /// <returns>Value LSB first</returns>
        public short GetShort(int startPosition)
        {
            // Do we have room in the buffer?
            if (_data == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition + 2 > _data.Length)
                throw new ArgumentOutOfRangeException("startPosition");

            return (short)(_data[startPosition] + (_data[startPosition + 1]) * 0x100);
        }
        /// <summary>
        /// Set the short value
        /// </summary>
        /// <param name="startPosition">Offset of the short</param>
        /// <param name="value">Value to set</param>
        public void SetShort(int startPosition, int value)
        {
            // Do we have room in the buffer?
            if (value == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition > _data.Length - 2)
                throw new ArgumentOutOfRangeException("startPosition");

            _data[startPosition] = (byte)(value & 0xFF);
            _data[startPosition + 1] = (byte)(value >> 8);
        }

        /// <summary>
        /// Get the unsigned short at the offset
        /// </summary>
        /// <param name="startPosition">Offset of the ushort</param>
        /// <returns>Value LSB first</returns>
        public ushort GetUShort(int startPosition)
        {
            // Do we have room in the buffer?
            if (_data == null)
                throw new ArgumentNullException("data");

            if (startPosition + 2 > _data.Length)
                throw new ArgumentOutOfRangeException("startPosition");

            return (ushort)(_data[startPosition] + ((_data[startPosition + 1]) * 0x100));
        }
        /// <summary>
        /// Set the ushort value at the offset specified
        /// </summary>
        /// <param name="startPosition">Offset to set the value at</param>
        /// <param name="value">Value to set</param>
        public void SetUShort(int startPosition, int value)
        {
            // Do we have room in the buffer?
            if (value == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition > _data.Length - 2)
                throw new ArgumentOutOfRangeException("startPosition");

            ushort uValue = (ushort)value;

            _data[startPosition] = (byte)(uValue & 0xFF);
            _data[startPosition + 1] = (byte)(uValue >> 8);
        }

        /// <summary>
        /// Get a long value at the offset
        /// </summary>
        /// <param name="startPosition">Offset of the long</param>
        /// <returns>Value at the offset</returns>
        public Int32 GetInt32(int startPosition)
        {
            // Do we have room in the buffer?
            if (_data == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition + 2 > _data.Length)
                throw new ArgumentOutOfRangeException("startPosition");

            Int32 uValue = _data[startPosition]
            + (((Int32)_data[startPosition + 1]) * 0x100)
            + (((Int32)_data[startPosition + 2]) * 0x10000)
            + (((Int32)_data[startPosition + 3]) * 0x1000000);

            return uValue;
        }
        /// <summary>
        /// Set the long at the offset specified
        /// </summary>
        /// <param name="startPosition">Offset of the long</param>
        /// <param name="value">Value to set</param>
        public void SetInt32(int startPosition, long value)
        {
            // Do we have room in the buffer?
            if (value == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition > _data.Length - 4)
                throw new ArgumentOutOfRangeException("startPosition");

            Int32 uValue = (Int32)value;

            _data[startPosition] = (byte)(uValue & 0xFF);
            _data[startPosition + 1] = (byte)((uValue >> 8) & 0xFF);
            _data[startPosition + 2] = (byte)((uValue >> 16) & 0xFF);
            _data[startPosition + 3] = (byte)((uValue >> 24) & 0xFF);
        }

        /// <summary>
        /// Get the unsigned long at the position
        /// </summary>
        /// <param name="startPosition"></param>
        /// <returns>Value at the offset</returns>
        public UInt32 GetUInt32(int startPosition)
        {
            // Do we have room in the buffer?
            if (_data == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition > _data.Length - 4)
                throw new ArgumentOutOfRangeException("startPosition");

            UInt32 uValue = _data[startPosition]
            + (((UInt32)_data[startPosition + 1]) * 0x100)
            + (((UInt32)_data[startPosition + 2]) * 0x10000)
            + (((UInt32)_data[startPosition + 3]) * 0x1000000);

            return uValue;
        }
        /// <summary>
        /// Set the unsigned long at the offset specified
        /// </summary>
        /// <param name="startPosition">Offset of the uint32</param>
        /// <param name="value">Value to set</param>
        public void SetUInt32(int startPosition, long value)
        {
            // Do we have room in the buffer?
            if (value == null)
                throw new ArgumentNullException("data");

            // Validate the offset
            if (startPosition > _data.Length - 4)
                throw new ArgumentOutOfRangeException("startPosition");

            UInt32 uValue = (UInt32)value;

            _data[startPosition] = (byte)(uValue & 0xFF);
            _data[startPosition + 1] = (byte)((uValue >> 8) & 0xFF);
            _data[startPosition + 2] = (byte)((uValue >> 16) & 0xFF);
            _data[startPosition + 3] = (byte)((uValue >> 24) & 0xFF);
        }
    }
}
